跳到主要内容

.riv 文件格式

.riv 文件是从 Rive 编辑器导出的二进制运行时格式,被所有 Rive 运行时使用。

运行时格式

Rive 编辑器将你的项目导出为 .riv 文件,供 Rive 运行时使用。这是画板、形状、动画、状态机等的二进制表示。Rive 运行时读取此文件以在应用程序、游戏、网站等中显示内容。该格式设计为在快速加载、小文件体积以及适应未来变化/新增功能之间取得平衡。

二进制类型

Rive 运行时文件的二进制读取器需要能够从流中读取以下数据类型。

注意: 字节序为小端序(little endian)。

类型描述
可变无符号整数LEB128 可变编码的无符号整数(以下简称 varuint)
无符号整数4 字节无符号整数
字符串无符号整数后跟指定长度的 UTF-8 编码字节数组
浮点数按 4 字节 IEEE 754 编码的 32 位浮点数

注意: 参考二进制读取器

文件头

文件头是写入文件的第一部分内容,为运行时提供基本信息以验证它能否读取此文件。其中包含一个 ToC(目录/字段定义),使运行时能够理解如何跳过它可能不理解的内容和对象。这也是该格式能够适应编辑器未来变化/新增功能的原因之一。较旧的运行时至少可以尝试加载旧文件并显示它,只是不包含它不理解的对象和属性。

类型
指纹4 字节
主版本号varuint
次版本号varuint
文件 IDvaruint
ToC字节对齐的位数组

指纹

文件指纹使导入器能够快速检查它确实在查看一个由 Rive 导出的文件。这是 4 个字节,表示 UTF-8/ASCII 字符串 "RIVE"。在十六进制编辑器中显示如下:

注意: 0x52 0x49 0x56 0x45 / "RIVE"

主版本号

运行时仅兼容单一的 Rive 导出格式主版本。当前主版本格式为 7。如果支持格式主版本 7 的运行时遇到格式主版本 6 的文件,它将立即报错且不会尝试读取任何后续内容,因为这两种格式被认为是根本不同的。这是 Rive 在需要时从根本上改变其导出格式的最后手段,我们会尽量少用。

从格式 6 到格式 7 的切换发生在多年前,编辑器已不再导出格式 6 的文件。

如果将来引入新的主版本格式,运行时和导出文件必须匹配主版本号。实际影响如下:

  • 如果更新到支持新主版本格式的运行时,则必须将文件重新导出为该主版本格式
  • 如果继续使用旧运行时,则必须以旧的兼容主版本格式导出文件
  • 添加对新文件格式主版本支持的运行时,也会发布一个运行时大版本更新

示例:假设 Android 运行时当前主版本为 11,当引入对新的文件格式主版本的支持时,对该格式的 Android 运行时支持将在主版本 12 中发布。要采用它,你需要更新到 Android 运行时 12 并将文件重新导出为新的文件格式主版本。

如果需要以编程方式验证文件的格式版本,请从文件头读取 Major VersionMinor Version 值。当文件格式不兼容时,Rive 运行时也会显示描述性的导入错误。

注意: 主版本之间不兼容。例如,支持格式版本 6 的运行时无法读取格式版本 7 的文件,反之亦然。每当引入新的主文件格式版本时,所有支持的运行时会发布相应的大版本更新。

次版本号

只要主版本号相同,次版本号的变化是相互兼容的。但是,如果运行时的次版本号不同,某些较新的功能可能不可用。例如,主版本 7 引入了状态机。我们正在为状态机添加新的状态类型。版本 7.0 的运行时可能无法加载 7.1 文件中导出的所有状态。但是,运行时仍然可以播放状态机,只是在转换到它不理解的状态时不会执行任何操作。

注意: 当文件使用了较旧的兼容运行时不支持的新功能时,文件仍然可以加载,且支持的功能会继续正常工作。不支持的功能会被视为空操作。Rive 运行时设计为避免在此兼容场景下发生崩溃或未定义行为。

版本兼容性示例:

运行时版本文件版本兼容性
6.16.0
6.16.2
6.17.0
7.06.1
7.07.1

文件 ID

这是文件的唯一标识符,将来可以通过我们的 API 用于区分文件。API 尚未定义,但计划的功能包括按需重新导出文件的新版本、获取文件详情等。目前,这可用于验证此导出是从哪个文件生成的。

ToC

文件头的目录(Table of Contents)部分是文件中所有属性及其底层类型的列表。这使运行时能够跳过它想跳过或不理解的属性。它通过为每个属性 ID 提供底层类型来实现此目的。

字段类型

有 5 种基本底层类型,但它们以 4 种不同的方式进行序列化。了解类型的序列化方式使运行时能够知道如何读取它。即使读取了错误的值或解释不正确,重要的是能够读取过去,以便安全地读取文件的其余部分。

例如,布尔值可以读取为无符号整数,因为底层类型和序列化器是兼容的。即使将布尔值作为整数读取不会提供该属性的有效值,运行时仍然可以读取过去。

ToC 数据

已知属性列表序列化为一系列可变无符号整数,以 0 作为终止符。有效的属性键由非零的无符号整数 ID/键来区分。属性之后是一个位数组,由读取的属性数量 / 4 字节组成。每个属性获得 2 位来定义可以使用哪种底层类型反序列化器来读取过去。

注意: 这里的意图是提供已知的属性类型键及其底层类型,这样如果属性类型未知,读取器可以读取整个值而不会出现缓冲区读取不足或溢出的问题。

底层类型2 位值
Uint/Bool0
String1
Float2
Color3

举例来说,假设一个文件有三个已知属性类型(属性 12 是 uint 值、属性 16 是 string 值、属性 6 是 bool 值),导出器将按如下方式序列化数据:

varuint: 12

varuint: 16

varuint: 6

varuint: 0

2 bits: 0

2 bits: 1

2 bits: 0

注意: 参考 ToC 反序列化器

基线属性

Rive 不会导出自上一个主版本以来系统已知的属性。我们在切换到新的主版本时进行基线化,因为不会有需要读取较新属性的次版本。在切换到最新主版本后新引入的属性,将随着新次版本的发布而导出。

内容

文件的其余部分就是一个对象列表,每个对象包含其属性和值的列表。对象以 varuint 类型键表示,紧接着是属性列表。属性以 0 varuint 终止。如果读取到非 0 值,则预期它是属性的类型键。如果运行时知道该类型键,它会知道底层类型以及如何解码。类型键后面的字节将是前面指定的二进制类型之一。如果未知,它可以从 ToC 中确定底层类型并读取过去。

Core 定义

所有对象和属性定义在一组我们称为 Core Definitions 的文件中。它们以一系列 JSON 对象定义,帮助 Rive 生成序列化、反序列化和动画属性代码。C++ 和 Flutter 运行时都有辅助工具来读取和生成这些类型的大量样板代码。

对象

Core 对象由其 Core 类型键表示。例如,Shape 的 core 类型键为 3。同样,你可以看到 C++ 运行时生成的代码也使用相同的键标识 Shape

属性

属性同样由 Core 类型键表示。这些键在所有对象中唯一,因此属性键 13 始终是 Node 对象的 X 值,并在运行时中匹配。Node 的 X 值已知为浮点值,因此当遇到它时会按浮点数解码。属性键 0 保留为空终止符(表示当前对象的属性已读取完毕)。

序列化对象示例

数据类型/大小描述
2varuint类型为 2 的对象(Node)
13varuintNode 的 X 属性
100.04 字节浮点数Node 的 X 值
14varuintNode 的 Y 属性
22.04 字节浮点数Node 的 Y 值
0varuint空终止符。属性读取完毕,Node 读取完成。

上下文

对象总是在彼此的上下文中提供。Shape 总是在 Artboard 之后提供。Node 的 artboard 始终可以通过查找最近读取的 Artboard 来确定。这个概念被广泛用于为需要上下文的对象提供上下文。另一个示例:KeyFrame 总是在 LinearAnimation 之后提供,这意味着你始终可以通过跟踪最后一个读取的 LinearAnimation 来确定 KeyFrame 属于哪个 LinearAnimation。

层级结构

Artboard 内的对象可以父子关联到 Artboard 中的其他对象。这种映射更为复杂,需要标识符来查找父对象。标识符以 core def 属性的形式提供。该值始终是一个无符号整数,表示 Artboard 内作为有效父对象的 ContainerComponent 派生对象的索引。

注意: 关于导入上下文的具体细节,可以查看 File 读取器中使用的 ImportStack 模式。Dart C++